#!/bin/sh
#-------------------------------------------------------------------------+
# Copyright (c) 2015, pannon
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#------------------------------------------------------------------------+

PATH='/bin:/usr/sbin:/usr/bin:/sbin:/opt/local/bin/'

# If we are on SmartOS source the config file
if [ $(uname) == 'SunOS' ] ; then
    . /opt/custom/etc/sdc-headnode-backup.conf
fi

snapshot="backup"
sdc_usb_config="/usbkey/config"
arglen="$1"
upload_cmd=""

__help () {
cat << 'EOT'

Usage: sdc-headnode-backup [ global | UUID | all | -f filename | usb | help | -h ]

Description:

  Simple script to backup all or selected zones.
  Each zone is recursively snapshoted and sent
  to an SSH gateway zone where it expects to
  upload the snapshot to Manta (with mput) or an
  S3 compatible object store via s3cmd.

  Additionally the headnode config is backed up.

Options:

  global   backup global zone bits (/opt /var /etc/zones)
  UUID     backup a single zone identified by UUID
  all      backup everything (global, non-global and usb config)
  -f       backup zones listed in file (one UUID per line)
  usb      backup usb config file
  json     backup zone_uuid.json configuration
  help|-h  display this help

Requires:

  - SmartOS
  - SSH zone with s3cmd (installed/configured)
  - SSH pubkey auth from headnode to SSH zone
  - mput (npm install -g manta)
  - s3cmd 1.5.x or higher (pip install s3cmd)
  - configuration file /opt/custom/etc/sdc-headnode-backup.conf

Assumptions:

  - this script is installed in /opt/custom/bin/sdc-headnode-backup
  - needs to run on headnode
  - SSH pubkey authentication from headnode to SSH zone
  - mput flags are configured in sdc-headnode-backup.conf
  - s3cmd is configured to access the bucket (sdc-headnode-backup.conf)
  - SSH zone/box with Manta or s3 access

         [headnode]
             |
           (ssh)
             |
         [SSH zone]
          /     \
      (s3cmd)  (mput)
        /         \
 [s3 storage]  [Manta storage]
    (LeoFS)

EOT
}

__backup_json () {
    # save UUID.json configs - just in case we need these..
    echo "INFO: backing up zone_uuid.json configuration files.."
    for zone in $(vmadm list -Ho uuid) ; do
        vmadm get $zone | ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
        "${upload_cmd}/$zone.json"
    done
}

# Backup bits required for successful headnode restore.
#  * /etc/zones
#  * /var (see excludes)
#  * /opt
#  * UUID.json for zones
#------------------------------------------------------
__backup_global_zone () {
    echo "INFO: backing up global zone bits.."
    # backup zone *.xml files
    gtar -czf - /etc/zones | \
    ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
    "${upload_cmd}/globalzone/etc-zones.tgz"

    # backup /var excluding tmp and crash
    gtar --exclude='var/tmp' \
         --exclude='var/crash' \
         --exclude='var/log' -czf - /var | \
    ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
    "${upload_cmd}/globalzone/var.tgz"

    # backup /opt
    zfs snapshot zones/opt@${snapshot}
    zfs send -v zones/opt@${snapshot} | gzip -1 | \
    ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
    "${upload_cmd}/globalzone/opt.zfs.gz"
    zfs destroy zones/opt@${snapshot}
}

# Snapshot the zone and zfs send to the SSH zone where the image
# is uploaded with inline with mput or s3cmd an object store.
# Expects one argument: zone UUID
__snap_and_send () {
    _zone="$1"
    _size="$(zfs get -Ho value used zones/${_zone})"

    echo "Processing: ${_zone}.zfs"
    echo "      Size: ${_size}"

    zfs snapshot -r zones/${_zone}@${snapshot}

    zfs send -vR zones/${_zone}@${snapshot} | gzip -1 | \
    ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
    "${upload_cmd}/${_zone}.zfs.gz"

    zfs destroy -r zones/${_zone}@${snapshot}
    __fetch_origin ${_zone}
}

# backup all zones
__backup_all () {
    _zones="$(vmadm list -Ho uuid)"

    echo "INFO: backing up all zones.."
    for _zone in $_zones ; do
        if [ ! -z $_zone ] ; then
            __snap_and_send $_zone
        fi
    done
}

# backup zones listed in file, one UUID per line
__backup_list () {
    _list="$1"

    if [ ! -z $_list ] && [ -e $_list ] ; then
        for _zone in $(cat $_list) ; do

            # check if line looks like UUID
            if [ ${#_zone} -eq "36" ] ; then
                __snap_and_send $_zone
                vmadm get $_zone | ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
                "${upload_cmd}/${_zone}.json"
            fi

        done
    else
        echo "Error: file $_list not found"
    fi
}

# This is required for clones, look up origin ZFS datasets
# and backup those as well (required for restoring cloned zones)
__fetch_origin () {
    _dataset="$1"
    _origin="$(zfs get -Ho value origin zones/$1)"

    if [ $_origin != '-' ] ; then
        _zone=$(echo $_origin | cut -f 2 -d/ | cut -f 1 -d @)
        echo "INFO: backing up origin dataset.."
        __snap_and_send $_zone
    fi
}

# Take a copy of the current USB configuration
__backup_usb_config () {
    echo "Backing up SDC USB config.."
    cat ${sdc_usb_config} | \
    ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
    "${upload_cmd}/globalzone/usbkey/config"
}

# Check whether to use s3cmd or Manta
if [ $use_s3 -eq 1 ] ; then
    export upload_cmd="s3cmd put $s3cmd_flags - s3://$s3bucket"
elif [ $use_manta -eq 1 ] ; then
    export upload_cmd="mput $mput_flags $mput_destination"
else
    echo "Error: both Manta and s3 is disabled"
fi

# a primitive UUID guess, backup a single zone by UUID
if [ ${#arglen} -eq "36" ] ; then
    __snap_and_send "$1"
    vmadm get $1 | ssh -i ${ssh_key} ${ssh_user}@${ssh_host} \
    "${upload_cmd}/$1.json"
    exit
fi

while [ $# ] ; do
    case "$1" in
        help|-h)    __help
                    exit
            ;;
        usb)        __backup_usb_config
                    exit
            ;;
        global)     __backup_global_zone && __backup_usb_config && \
                    __backup_json
                    exit
            ;;
        all)        __backup_all && __backup_global_zone && \
                    __backup_usb_config && __backup_json
                    exit
            ;;
        json)       __backup_json
                    exit
            ;;
        -f)         __backup_list "$2" && __backup_global_zone && \
                    __backup_usb_config && __backup_json
                    exit
            ;;
        *)          __help
                    exit
            ;;
    esac
    shift
done
